package com.orange.delivery.dao.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.stereotype.Repository;

import com.orange.delivery.dao.MarketRepositoryCustom;
import com.orange.delivery.model.persistent.Market;
import com.orange.delivery.technicalservice.tool.MultiValuedMap;

@Repository
public class MarketRepositoryImpl implements MarketRepositoryCustom {

	@PersistenceContext
	private EntityManager em;

	private final static String COUNT_WITH_EXACT_NAME = "SELECT COUNT(x) FROM Market x where x.name = :name";

	public boolean existsMarketWithName(String nom) {

		TypedQuery<Number> query = em.createQuery(COUNT_WITH_EXACT_NAME, Number.class);

		query.setParameter("name", nom);
		Number result = query.getSingleResult();
		return (result.intValue() == 0) ? false : true;

	}

	public Page<Market> findByCriteria(Pageable pageInfo, MultiValuedMap<String, String> criteria) {

		CriteriaBuilder builder = em.getCriteriaBuilder();

		// 2 queries : 1 for searching elements, 1 for counting results
		CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
		CriteriaQuery<Market> query = builder.createQuery(Market.class);

		Root<Market> root = query.from(Market.class);
		Root<Market> countRoot = countQuery.from(Market.class);

		// adds predicates to both queries, based on criterias
		countQuery = buildSearchQuery(builder, countQuery, countRoot, criteria);
		countQuery.select(builder.count(countRoot));

		query = buildSearchQuery(builder, query, root, criteria);
		query.select(root);

		// Query for counting elements
		TypedQuery<Long> tqc = em.createQuery(countQuery);
		long total = tqc.getSingleResult();

		// Sorting
		Sort sort = pageInfo == null ? null : pageInfo.getSort();
		if (sort != null) {
			query.orderBy(QueryUtils.toOrders(sort, root, builder));
		}

		// Search query
		TypedQuery<Market> tq = em.createQuery(query);
		int pageSize = pageInfo.getPageSize();
		tq.setFirstResult(pageInfo.getOffset());
		tq.setMaxResults(pageSize);

		// search results
		List<Market> res = tq.getResultList();

		// return results page
		return new PageImpl<Market>(res, pageInfo, total);

	}

	private <T, V> CriteriaQuery<T> buildSearchQuery(CriteriaBuilder builder, CriteriaQuery<T> cq, Root<V> root,
			MultiValuedMap<String, String> criteria) {

		Predicate predicate = null;
		List<Predicate> predicates = new ArrayList<Predicate>();

		Set<Map.Entry<String, List<String>>> entrySet = criteria.entrySet();
		// building OR predicates (different values for one property)
		for (Map.Entry<String, List<String>> entry : entrySet) {
			String param = entry.getKey();
			Path<?> pathParam = getPath(param, root);
			Predicate pTemp = null;
			boolean first = true;
			for (String value : entry.getValue()) {
				if (first) {
					first = false;
					pTemp = builder.equal(pathParam, value);
				} else {
					pTemp = builder.or(pTemp, builder.equal(pathParam, value));
				}
			}
			if (pTemp != null) {
				predicates.add(pTemp);
			}
		}

		// building AND predicates (different properties)
		boolean firstPredicate = true;
		for (Predicate other : predicates) {
			if (firstPredicate) {
				firstPredicate = false;
				predicate = other;
			} else {
				predicate = builder.and(predicate, other);
			}
		}
		if (predicate != null) {
			cq.where(predicate);
		}

		return cq;

	}

	private <V> Path<?> getPath(String name, Root<V> root) {

		StringTokenizer st = new StringTokenizer(name, ".");
		Path<?> path = null;
		boolean first = true;
		String param = null;
		while (st.hasMoreElements()) {
			param = (String) st.nextElement();
			if (!first) {
				path = path.get(param);
			} else {
				if (st.hasMoreElements() && param.endsWith("s")) {
					// takes collections into account
					path = root.join(param);
				} else {
					path = root.get(param);
				}
				first = false;
			}

		}
		return path;
	}

}
